/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package client;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.table.DefaultTableModel;
import util.Utilities;
import model.Message;
import model.FileInfo;
import model.Map;
import model.RevisionController;

import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import DB.*;


/**
 *
 * @author Hallucinogen
 */
public class Client {
    public Client(DefaultTableModel model) {
        mModel = model;
    }

    public boolean isLoginSuccess(String user, String pass){
        boolean rv = false;
         try {
            System.out.println(user + " " + pass);
            mClientName = user;

            DB db = new DB();
            db.executeQuery("SELECT username,password FROM user WHERE username='" + user+"' AND password='"+pass+"'");
            ResultSet rs = db.getResultSet();
            rs.next();
            rv = rs.isLast();
            
        } catch (SQLException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
        return rv;
    }

    public boolean isLogoutSuccess(String user, String pass){

        return true;
    }

    public RevisionController getServerVersionControl() throws IOException {
        RevisionController retval = null;
        mSocket         = getSocket();
        InputStream is  = mSocket.getInputStream();
        Object[] param = new Object[1];

        // set file yang dicari sesuai dengan username nya
        param[0] = Utilities.SERVER_REPO + "/" + mClientName;
        Message message = new Message(Message.MSG_FILEINFO, param);
        Message.sendMessage(mSocket, message);

        // baca message type
        int attempt = 0;
        boolean berhasil = false;
        while (attempt < 10 && !berhasil) {
            ++attempt;

            int messageType     = is.read();
            int messageLength   = 0;

            if (messageType == Message.MSG_KEEP_ALIVE || messageType == -1) continue;

            // baca message length
            messageLength = (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;

            // baca binary
            byte[] ret = new byte[messageLength - 5];
            int totalRead = 0;
            if (messageLength > 4096) {
                byte[] buffer = new byte[4096];
                int read;

                while (totalRead < messageLength - 5 && (read = is.read(buffer)) > 0) {
                    for (int i = 0; i < read; ++i) {
                        ret[totalRead + i] = buffer[i];
                    }

                    totalRead += read;
                }
            } else {
                totalRead = is.read(ret);
            }

            param = Message.readMessage(messageType, messageLength, ret);

            retval = RevisionController.createFromByte((byte[])param[1]);
            
            berhasil = true;
            
        }
        
        return retval;
    }

    public RevisionController getClientVersionControl() throws IOException {
        RevisionController retval = new RevisionController();
        ArrayList<FileInfo> metadatas = retval.getFileInfos();

        for (int i = 0; i < mModel.getRowCount(); ++i) {
            String serverName = mModel.getValueAt(i, 0).toString();
            String localPath =  mModel.getValueAt(i, 1).toString();
            
            RevisionController temp = RevisionController.getVersionControl(localPath);

            ArrayList<FileInfo> tempMeta = temp.getFileInfos();

            for (int j = 0; j < tempMeta.size(); ++j) {
                FileInfo push = tempMeta.get(j);

                metadatas.add(push);
            }
        }

        return retval;
    }

    public RevisionController compareVersionControl(RevisionController clientVersionControl,
            RevisionController serverVersionControl,
            ArrayList<Map<String, String>> listToDownload,
            ArrayList<Map<String, String>> listToUpload,
            ArrayList<String> listToDeleteLocal,
            ArrayList<String> listToDeleteRemote) throws IOException {
        String repo = Utilities.SERVER_REPO + "/" + mClientName;

        RevisionController retval = new RevisionController();
        ArrayList<FileInfo> version = retval.getFileInfos();
        ArrayList<FileInfo> server  = serverVersionControl.getFileInfos();
        ArrayList<FileInfo> client  = clientVersionControl.getFileInfos();
        ArrayList<FileInfo> unhandledServerMeta = new ArrayList<FileInfo>();

        System.out.println("server size: "+ server.size());
        for (int i = 0; i < server.size(); ++i)
            unhandledServerMeta.add(server.get(i));

        // client ngecek server
        for (int i = 0; i < client.size(); ++i) {
            boolean found = false;
            FileInfo clientMeta     = client.get(i);
            String clientFilePath   = clientMeta.getFilePath();
            System.out.println("filepath client "+clientFilePath);
            String sharingPath      = "";
            int clientVersion       = clientMeta.getVersion();

            for (int j = 0; j < mModel.getRowCount(); ++j) {
                String temp2 = mModel.getValueAt(j, 1).toString();
                if (clientFilePath.startsWith(temp2)) {
                    sharingPath = mModel.getValueAt(j, 0).toString() + "/" + clientFilePath.substring(temp2.length() + 1, clientFilePath.length());
                    break;
                }
            }

            for (int j = 0; j < server.size(); ++j) {
                FileInfo serverMeta     = server.get(j);
                String serverFilePath   = serverMeta.getFilePath();
                if (serverFilePath.substring(repo.length() + 1, serverFilePath.length()).equals(sharingPath)) {
                    int serverVersion = serverMeta.getVersion();

                    if (serverVersion >= clientVersion) {
                        // server versi lebih baru, overwrite
                        
                        //cek apakah server nya ngedelete file ini
                        if (serverMeta.getFileType() == Utilities.TYPE_DELETED) {
                            // wah kedelete, brarti delete yang local
                            listToDeleteLocal.add(clientFilePath);
                        } else {
                            // wah ke update, brarti perlu download nich
                            System.out.println(new Map<String, String>(clientFilePath, serverFilePath));
                            listToDownload.add(new Map<String, String>(clientFilePath, serverFilePath));
                            System.out.println("masuk0");
                            
                            // masukin meta terbaru
                            FileInfo pushMeta = serverMeta;
                            pushMeta.setVersion(pushMeta.getVersion() + 1);
                            pushMeta.setFilePath(sharingPath);
                            version.add(pushMeta);
                        }
                    } else {
                        // client versi lebih baru, overwrite

                        // cek apakah clientnya ngedelete file ini
                        if (clientMeta.getFileType() == Utilities.TYPE_DELETED) {
                            // wah kedelete, brarti delete yang di server
                            listToDeleteRemote.add(serverFilePath);
                        } else {
                            // wah ke update, brarti perlu upload nich
                            listToUpload.add(new Map<String, String>(clientFilePath, serverFilePath));

                            FileInfo pushMeta = clientMeta;
                            pushMeta.setVersion(pushMeta.getVersion() + 1);
                            pushMeta.setFilePath(sharingPath);
                            version.add(pushMeta);
                        }
                    }

                    // buang path yang ke handle
                    unhandledServerMeta.remove(serverMeta);
                    found = true;
                    break;
                }
            }

            if (!found) {
                // duh kok ga ketemu yach? Brarti ini barang ga ada di server

                // cek apakah file ini harusnya didelete
                if (clientMeta.getFileType() == Utilities.TYPE_DELETED) {
                    // wah dia harusnya didelete, dan diserver ga ada, ya udah diemin aja
                } else {
                    // ups, dia ga seharusnya didelete, tapi server ga punya! Upload gan!
                    listToUpload.add(new Map<String, String>(clientFilePath, repo + "/" + sharingPath));

                    FileInfo pushMeta = clientMeta;
                    pushMeta.setVersion(pushMeta.getVersion() + 1);
                    pushMeta.setFilePath(sharingPath);
                    version.add(pushMeta);
                }
            }
        }

        // cek semua path di server yang ga ada di local
        for (int i = 0; i < unhandledServerMeta.size(); ++i) {
            FileInfo serverMeta     = unhandledServerMeta.get(i);
            System.out.println("serverMeta: " + serverMeta.getFilePath());
            String serverFilePath   = serverMeta.getFilePath();
            String sharingPath      = serverFilePath.substring(repo.length() + 1, serverFilePath.length());

            // cek apakah file ini harusnya didelete?
            if (serverMeta.getVersion() == Utilities.TYPE_DELETED) {
                 // wah dia harusnya didelete, dan client ga ada, ya udah diemin aja
            } else {
                String mainSharingPath = sharingPath;
                int haha = sharingPath.indexOf("/");
                if (haha > 0)
                    mainSharingPath = sharingPath.substring(0, haha);

                // cari path yang bersesuaian
                boolean found = false;
                for (int j = 0; j < mModel.getRowCount(); ++j) {
                    if (mainSharingPath.equals(mModel.getValueAt(j, 0).toString())) {
                        // wah ketemu, skarang kita coba download barang2 kesana
                        String posDownload = mModel.getValueAt(j, 1).toString();
                        haha = posDownload.lastIndexOf("/");
                        if (haha > 0)
                            posDownload = posDownload.substring(0, haha);
                        System.out.println("diatas masuk1: " + new Map<String, String>(posDownload + "/" + sharingPath, serverFilePath));
                        listToDownload.add(new Map<String, String>(posDownload + "/" + sharingPath, serverFilePath));
                        System.out.println("masuk1");
                        found = true;

                        FileInfo pushMeta = serverMeta;
                        pushMeta.setVersion(pushMeta.getVersion() + 1);
                        pushMeta.setFilePath(sharingPath);
                        version.add(pushMeta);

                        break;
                    }
                }

                // kalo ga ketemu, ini yang gawat
                if (!found) {
                    // kita masukin aja ke posisi aplikasi sekarang
                    Object[] param = new Object[2];
                    param[0] = mainSharingPath;
                    param[1] = "unmanaged/" + sharingPath;
                    mModel.addRow(param);

                    FileInfo pushMeta = serverMeta;
                    pushMeta.setVersion(pushMeta.getVersion() + 1);
                    pushMeta.setFilePath(sharingPath);
                    version.add(pushMeta);

                    listToDownload.add(new Map<String, String>("unmanaged/" + sharingPath, serverFilePath));
                }
            }
        }

        return retval;
    }

    public void downloadFile(String clientPath, String serverPath) throws IOException {
        mSocket         = getSocket();
        InputStream is  = mSocket.getInputStream();
        Object[] param  = new Object[2];
        param[0]        = clientPath;
        param[1]        = serverPath;

        Message message = new Message(Message.MSG_DOWNLOAD, param);
        Message.sendMessage(mSocket, message);

        // baca message type
        int attempt = 0;
        boolean berhasil = false;
        while (attempt < 10 && !berhasil) {
            ++attempt;
            
            int messageType     = is.read();
            int messageLength   = 0;

            if (messageType == Message.MSG_KEEP_ALIVE || messageType == -1) continue;

            // baca message length
            messageLength = (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;
            messageLength = (messageLength << 8) + (is.read() + 256) % 256;

            // baca binary
            byte[] ret = new byte[messageLength - 5];
            int totalRead = 0;
            if (messageLength > 4096) {
                byte[] buffer = new byte[4096];
                int read;

                while (totalRead < messageLength - 5 && (read = is.read(buffer)) > 0) {
                    for (int i = 0; i < read; ++i) {
                        ret[totalRead + i] = buffer[i];
                    }

                    totalRead += read;
                }
            } else {
                totalRead = is.read(ret);
            }

            param = Message.readMessage(messageType, messageLength, ret);

            try {
                System.out.println("[Client] Handling download message " + param[0]);
                File file = new File((String)param[0]);
                if (((byte[])param[1]).length == 0) {
                    file.mkdirs();
                } else {
                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));

                    bos.write((byte[])param[1]);

                    bos.close();
                }
                System.out.println("[Client] Done handling download message " + param[0]);
                berhasil = true;
            } catch (IOException iEx) {

            }
        }
    }

    public void uploadFile(String clientPath, String serverPath) throws IOException {
        System.out.println("[Client] Handling upload message " + clientPath + " " + serverPath);
        byte[] res = Utilities.getFileByte(clientPath);

        Object[] param = new Object[2];
        param[0] = serverPath;
        param[1] = res;

        Message message = new Message(Message.MSG_UPLOAD, param);
        Message.sendMessage(getSocket(), message);
        System.out.println("[Client] Done upload message " + clientPath);
    }

    public void deleteLocalFile(String clientPath) {
        File file = new File(clientPath);

        if (file.exists()) {
            file.delete();
        }
    }

    public void deleteRemoteFile(String serverPath) throws IOException {
        Object[] param  = new Object[1];
        param[0]        = serverPath;
        Message message = new Message(Message.MSG_DELETE, param);
        Message.sendMessage(getSocket(), message);
    }

    public boolean isConnected() {
        return mSocket.isConnected();
    }

    public void synchronize () {
        ArrayList<Map<String, String>> toUpload    = new ArrayList<Map<String, String>>();
        ArrayList<Map<String, String>> toDownload  = new ArrayList<Map<String, String>>();
        ArrayList<String> toDeleteLocal     = new ArrayList<String>();
        ArrayList<String> toDeleteRemote    = new ArrayList<String>();

        try {
            RevisionController compromised = compareVersionControl(getClientVersionControl(), getServerVersionControl(), toDownload, toUpload, toDeleteLocal, toDeleteRemote);
            
            System.out.println("[Client] Uploading " + toUpload.size() + " files");
            for (int i = 0 ; i < toUpload.size(); ++i)
                uploadFile(toUpload.get(i).First, toUpload.get(i).Second);

            System.out.println("[Client] Downloading " + toDownload.size() + " files");
            for (int i = 0 ; i < toDownload.size(); ++i){
                System.out.println("iterasi ke: " + i);
                System.out.println("to download first: " + toDownload.get(i).First);
                System.out.println("to download second: " + toDownload.get(i).Second);
                downloadFile(toDownload.get(i).First, toDownload.get(i).Second);            
            }
            System.out.println("[Client] Deleting " + toDeleteLocal.size() + " local files");
            for (int i = 0 ; i < toDeleteLocal.size(); ++i)
                deleteLocalFile(toDeleteLocal.get(i));

            System.out.println("[Client] Deleting " + toDeleteRemote.size() + " remote files");
            for (int i = 0 ; i < toDeleteRemote.size(); ++i)
                deleteRemoteFile(toDeleteRemote.get(i));

            RevisionController clientVersion = new RevisionController(compromised);
            ArrayList<FileInfo> clientMetas = clientVersion.getFileInfos();

            for (int i = 0; i < clientMetas.size(); ++i) {
                FileInfo meta = clientMetas.get(i);
                String clientFolder = meta.getFilePath();

                for (int j = 0; j < mModel.getRowCount(); ++j) {
                    String folderSharing = mModel.getValueAt(j, 0).toString();
                    if (clientFolder.startsWith(folderSharing)) {
                        meta.setFilePath(clientFolder.replaceFirst(folderSharing, mModel.getValueAt(j, 1).toString()));
                    }
                }
            }

            for (int i = 0; i < mModel.getRowCount(); ++i) {
                clientVersion.setRevisionToFile(mModel.getValueAt(i, 1).toString(), true);
            }

            /// TODO : kirim compromised ke server
            Object[] param = new Object[2];
            param[0] = Utilities.SERVER_REPO + "/" + mClientName;

            for (int i = 0; i < compromised.getFileInfos().size(); ++i) {
                FileInfo meta = compromised.getFileInfos().get(i);
                
                System.out.println("metafilepath: "+meta.getFilePath());
                meta.setFilePath(Utilities.SERVER_REPO + "/" + mClientName + "/" + meta.getFilePath());
            }
            param[1] = compromised.toSend();
            Message message = new Message(Message.MSG_FILEINFO_RESP, param);
            Message.sendMessage(mSocket, message);
        } catch (IOException iEx) {
            System.out.println("Error caused by " + iEx);

            
        }
    }

    public Socket getSocket() throws IOException {
        if (mSocket == null || !mSocket.isConnected() || mSocket.isClosed())
            mSocket = new Socket(Utilities.SERVER_HOST, Utilities.SERVER_PORT);

        try {
            OutputStream os = mSocket.getOutputStream();
            // ngecek apakah socket nya masih idup pa ngga
            os.write(Message.MSG_KEEP_ALIVE);
            os.flush();
        } catch (IOException iEx) {
            System.out.println("Socket not connected");
            mSocket.close();
            mSocket = new Socket(Utilities.SERVER_HOST, Utilities.SERVER_PORT);
        }

        return mSocket;
    }

    private DefaultTableModel mModel;
    private Socket mSocket;
    private String mClientName;
    
}